﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the GNU General Public License version 2 (GPLv2).
// See accompanying file License.txt or copy at http://phuse.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Phuse.Net.Forums
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Threading;

    /// <summary>Provides methods to send/receive articles to/from a host.</summary>
    /// <typeparam name="TConnection">The type of the connection object this provider will use.</typeparam>
    internal sealed partial class SendReceiveHelper<TConnection> : ISendReceiveHelper where TConnection : Connection
    {
        private readonly IForumProvider<TConnection> forumProvider;
        private readonly SendReceiveContext context;
        private readonly bool receiveContents;
        private readonly IProgress<ISendReceiveProgress> progress;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        public Connection SendReceive(Connection existingConnection, CancellationToken cancellationToken)
        {
            using (var connectionGuard = ScopeGuard.Create(this.Connect(existingConnection, cancellationToken)))
            {
                try
                {
                    this.SendArticles(connectionGuard.Resource);
                    this.ReceiveHeaders(connectionGuard.Resource);
                    this.ReceiveContents(connectionGuard.Resource);
                    connectionGuard.Dismiss();
                    return connectionGuard.Resource;
                }
                catch (WriteFailedException)
                {
                    throw;
                }
                catch (ConnectionLostException)
                {
                    throw;
                }
                catch (IOException ex)
                {
                    throw new ConnectionLostException("Connection has been lost.", ex);
                }
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        internal SendReceiveHelper(
            IForumProvider<TConnection> forumProvider,
            SendReceiveContext context,
            bool receiveContents,
            IProgress<ISendReceiveProgress> progress)
        {
            this.forumProvider = forumProvider;
            this.context = context;
            this.receiveContents = receiveContents;
            this.progress = progress;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private TConnection Connect(Connection currentConnection, CancellationToken cancellationToken)
        {
            using (var operationContext = new ConnectContext(this.context, this.progress))
            {
                try
                {
                    using (var guard = ScopeGuard.Create(currentConnection))
                    {
                        var castConnection = currentConnection as TConnection;

                        if ((castConnection != null) && operationContext.Group.Host.Equals(castConnection.Host))
                        {
                            guard.Dismiss();
                            return castConnection;
                        }
                    }

                    operationContext.StartOperation();

                    using (var connectionGuard =
                        ScopeGuard.Create(this.forumProvider.Connect(operationContext.Group.Host, cancellationToken)))
                    {
                        ++operationContext.ItemIndex;
                        connectionGuard.Dismiss();
                        return connectionGuard.Resource;
                    }
                }
                catch (IOException ex)
                {
                    throw new HostUnreachableException("Host is unreachable.", ex);
                }
            }
        }

        private void SendArticles(TConnection connection)
        {
            using (var operationContext = new SendContext(this.context, this.progress))
            {
                if (operationContext.StartOperation())
                {
                    var exceptions = new List<Exception>();

                    foreach (var article in operationContext.Articles)
                    {
                        var message = article.LoadMessage();
                        article.SaveMessage(message); // Ensure that the date of the message is the current date
                        operationContext.Root.SubmitChanges();
                        var exception = this.forumProvider.SendArticle(connection, message);

                        if (exception == null)
                        {
                            article.SendStatus = SendStatus.Sent;
                            operationContext.Root.SubmitChanges();
                        }
                        else
                        {
                            exceptions.Add(exception);
                        }

                        ++operationContext.ItemIndex;
                    }

                    if (exceptions.Count > 0)
                    {
                        throw new AggregateException("At least one article could not be sent.", exceptions);
                    }
                }
            }
        }

        private void ReceiveHeaders(TConnection connection)
        {
            using (var operationContext = new ReceiveHeadersContext(
                this.context, this.receiveContents, this.progress, connection, this.forumProvider))
            {
                if (operationContext.StartOperation())
                {
                    this.forumProvider.ReceiveHeaders(
                        connection,
                        operationContext.Group.Name,
                        operationContext.LastOldArticle,
                        operationContext.ItemIndex,
                        operationContext.SaveHeader,
                        operationContext.SaveContent);
                    operationContext.Submit();
                }
            }
        }

        private void ReceiveContents(TConnection connection)
        {
            using (var operationContext = new ReceiveContentsContext(this.context, this.progress))
            {
                if (operationContext.StartOperation())
                {
                    IList<IInboundArticleInfo> contentsToReceive;

                    while ((contentsToReceive = operationContext.Root.GetContentsToReceive(
                        operationContext.Group, Cache.MaxBatchSize).Cast<IInboundArticleInfo>().ToList()).Count > 0)
                    {
                        this.forumProvider.ReceiveContents(
                            connection,
                            operationContext.Group.Name,
                            contentsToReceive,
                            operationContext.SaveContent,
                            operationContext.DeleteArticle);
                        operationContext.Submit();
                    }
                }
            }
        }
    }
}
